#include <iostream>
#include <cassert>
#include <gtkmm.h>
#include <iomanip>
#include "display.h"
#include "fractal.h"
#include "gradientchooser.h"
#include "sizedialog.h"
#include "cpu.h"

Display::Display(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade)
	: Gtk::Window(cobject), fractal_(NULL), nThreads_(getCpuInfo()->nCpus) {
	set_title("mgen");
	refGlade->get_widget("imagemenuitem_new", imagemenuitem_new_);
	imagemenuitem_new_->signal_activate().connect(sigc::mem_fun(*this, &Display::on_imagemenuitem_new_activate));
	refGlade->get_widget("imagemenuitem_save", imagemenuitem_save_);
	imagemenuitem_save_->signal_activate().connect(sigc::mem_fun(*this, &Display::on_imagemenuitem_save_activate));
	refGlade->get_widget("imagemenuitem_quit", imagemenuitem_quit_);
	imagemenuitem_quit_->signal_activate().connect(sigc::mem_fun(*this, &Display::on_imagemenuitem_quit_activate));
	for (int i = 0; i < MAX_SAMPLES; ++i) {
		std::ostringstream name;
		name << "radiomenuitem_aasamples_" << (i + 1);
		refGlade->get_widget(name.str(), radiomenuitem_aasamples_[i]);
		radiomenuitem_aasamples_[i]->signal_toggled().connect(sigc::bind<int>(sigc::mem_fun(*this, &Display::on_radiomenuitem_aasamples_toggled), i + 1));
	}
	refGlade->get_widget("menuitem_gradient", menuitem_gradient_);
	menuitem_gradient_->signal_activate().connect(sigc::mem_fun(*this, &Display::on_menuitem_gradient_activate));
	refGlade->get_widget("menuitem_size", menuitem_size_);
	menuitem_size_->signal_activate().connect(sigc::mem_fun(*this, &Display::on_menuitem_size_activate));

	refGlade->get_widget("eventbox", eventbox_);
	eventbox_->add_events(Gdk::POINTER_MOTION_MASK);
	eventbox_->signal_button_press_event().connect(sigc::mem_fun(*this, &Display::on_eventbox_button_press_event));
	eventbox_->signal_button_release_event().connect(sigc::mem_fun(*this, &Display::on_eventbox_button_release_event));
	eventbox_->signal_motion_notify_event().connect(sigc::mem_fun(*this, &Display::on_eventbox_motion_notify_event));
	eventbox_->signal_leave_notify_event().connect(sigc::mem_fun(*this, &Display::on_eventbox_leave_notify_event));
	eventbox_->signal_expose_event().connect(sigc::mem_fun(*this, &Display::on_eventbox_expose_event));
	refGlade->get_widget("image", image_);
	refGlade->get_widget("statusbar", statusbar_);
	refGlade->get_widget("progressbar", progressbar_);
	refGlade->get_widget("label", label_);
	on_imagemenuitem_new_activate();
	
	updateFractal_ = 0;
}

Display::~Display() {
	delete fractal_;
}

void Display::updateImage() {
	image_->set(fractal_->createPixbuf());
}

void Display::render() {
	time_ = time(NULL);
	fractal_->generate(nThreads_);
	label_->hide();
	progressbar_->set_fraction(0);
	progressbar_->set_text("");
	progressbar_->show();
	Glib::signal_timeout().connect(sigc::mem_fun(*this, &Display::update), 100);
}

bool Display::update() {
	if (fractal_->isComplete() == true) {
		progressbar_->hide();
		label_->set_text(Glib::ustring::compose("render time: %1 s", time(NULL) - time_));
		label_->show();
		updateImage();
		return false;
	} else {
		progressbar_->set_fraction(fractal_->getProgress());
		progressbar_->set_text(Glib::ustring::compose("%1 s", time(NULL) - time_));
		return true;
	}
}

void Display::on_imagemenuitem_new_activate() {
	if (fractal_ != NULL) {
		delete fractal_;
	}
	Gdk::Rectangle allocation = eventbox_->get_allocation();
	fractal_ = new Fractal(allocation.get_width(), allocation.get_height(), -0.5, 0.0, 0.004, 512, 1);
	radiomenuitem_aasamples_[0]->set_active(true);
	render();
}

void Display::on_imagemenuitem_save_activate() {
	Gtk::FileChooserDialog fileChooserDialog("Save File", Gtk::FILE_CHOOSER_ACTION_SAVE);
	fileChooserDialog.add_button("Cancel", Gtk::RESPONSE_CANCEL);
	fileChooserDialog.add_button("Save", Gtk::RESPONSE_OK);
	Gtk::FileFilter filterPng;
	filterPng.set_name("PNG image");
	filterPng.add_mime_type("image/png");
	fileChooserDialog.add_filter(filterPng);
	Gtk::FileFilter filterBmp;
	filterBmp.set_name("Bitmap");
	filterBmp.add_mime_type("image/bmp");
	fileChooserDialog.add_filter(filterBmp);
	Gtk::FileFilter filterJpeg;
	filterJpeg.set_name("JPEG image");
	filterJpeg.add_mime_type("image/jpeg");
	fileChooserDialog.add_filter(filterJpeg);
	if (fileChooserDialog.run() != Gtk::RESPONSE_OK) {
		return;
	}
	Glib::ustring filename = fileChooserDialog.get_filename();
	Glib::ustring type;
	if (filename.rfind(".png") == filename.size() - 4) {
		type = "png";
	} else if (filename.rfind(".bmp") == filename.size() - 4) {
		type = "bmp";
	} else if (filename.rfind(".jpg") == filename.size() - 4 || filename.rfind(".jpeg") == filename.size() - 5) {
		type = "jpeg";
	} else if (fileChooserDialog.get_filter() == &filterPng) {
		type = "png";
		filename += ".png";
	} else if (fileChooserDialog.get_filter() == &filterBmp) {
		type = "bmp";
		filename += ".bmp";
	} else if (fileChooserDialog.get_filter() == &filterJpeg) {
		type = "jpeg";
		filename += ".jpg";
	} else {
		assert(0);
	}
	fractal_->createPixbuf()->save(filename, type);
}

void Display::on_imagemenuitem_quit_activate() {
	hide();
}

void Display::on_radiomenuitem_aasamples_toggled(int nSamples) {
	if (radiomenuitem_aasamples_[nSamples - 1]->get_active() == true) {
		fractal_->setNSamples(nSamples);
	}
	render();
}

void Display::on_menuitem_gradient_activate() {
	Glib::RefPtr<Gtk::Builder> refBuilder = Gtk::Builder::create_from_file("gradientchooser.glade");
	GradientChooser* gradientChooser;
	refBuilder->get_widget_derived("dialog", gradientChooser);
	gradientChooser->setColours(&(fractal_->getGradient()));
	gradientChooser->setDisplay(this);
	gradientChooser->run();
	delete gradientChooser;
}


void Display::on_menuitem_size_activate() {
	Glib::RefPtr<Gtk::Builder> refBuilder = Gtk::Builder::create_from_file("sizedialog.glade");
	SizeDialog* sizeDialog;
	refBuilder->get_widget_derived("dialog", sizeDialog);
	sizeDialog->setFractal(fractal_);
	if (sizeDialog->run() == Gtk::RESPONSE_OK) {
		eventbox_->set_size_request(fractal_->getW(), fractal_->getH());
		render();
	}
	
	delete sizeDialog;
}


bool Display::on_eventbox_button_press_event(GdkEventButton* event) {
	dragX_ = (int)event->x;
	dragY_ = (int)event->y;
	return true;
}

bool Display::on_eventbox_button_release_event(GdkEventButton* event) {
	double centreX = fractal_->getCentreX();
	double centreY = fractal_->getCentreY();
	double scale = fractal_->getScale();
	if (abs((int)event->x - dragX_) > 1 || abs((int)event->y - dragY_) > 1) {
		centreX -= (event->x - dragX_) * scale;
		centreY -= (event->y - dragY_) * scale;
	} else {
		Gdk::Rectangle allocation = eventbox_->get_allocation();
		centreX += (event->x - (allocation.get_width() / 2)) * scale;
		centreY += (event->y - (allocation.get_height() / 2)) * scale;
		if (event->button == 1) {
			fractal_->setScale(scale * 0.5);
		} else if (event->button == 3) {
			fractal_->setScale(scale * 2.0);
		}
	}
	fractal_->setCentre(centreX, centreY);
	render();
	return true;
}

bool Display::on_eventbox_motion_notify_event(GdkEventMotion* event) {
	Gdk::Rectangle allocation = eventbox_->get_allocation();
	double scale = fractal_->getScale();
	double centreX = fractal_->getCentreX() + ((event->x - (allocation.get_width() / 2)) * scale);
	double centreY = fractal_->getCentreY() + ((event->y - (allocation.get_height() / 2)) * scale);
	std::ostringstream text;
	text << std::setprecision(-(int)(log(scale) / log(10))) << centreX << " + " << centreY << "i";
	statusbar_->push(Glib::ustring(text.str()), statusbar_->get_context_id("coordinates"));
	return true;
}

bool Display::on_eventbox_leave_notify_event(GdkEventCrossing* event) {
	// i don't know why both of these are needed, but they are
	statusbar_->pop(statusbar_->get_context_id("coordinates"));
	//statusbar_->remove_all_messages(statusbar_->get_context_id("coordinates"));
	//statusbar_->remove_all_messages(statusbar_->get_context_id("coordinates"));
	return true;
}

bool Display::on_eventbox_expose_event(GdkEventExpose* event) {
	Gdk::Rectangle allocation = eventbox_->get_allocation();
	if (abs(allocation.get_width()  - fractal_->getW()) > 1 || abs(allocation.get_height() - fractal_->getH()) > 1) {
		fractal_->setSize(allocation.get_width(), allocation.get_height());
		render();
	}
	return true;
}
